using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using InterfaceWeaver.AopCore;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public class MapDependencyPropertyAspectHandler : AspectHandlerBase<MapDependencyPropertyAttribute>
    {
        private bool skipInheritance = false;

        /// <summary>
        /// Determines whether the aspect is known and could be handled by aspect handler.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        public override bool HandleOnInterfacePreview(AspectDispatcher context, AttributeSyntax attributeSyntax)
        {
            this.skipInheritance = false;
            return base.HandleOnInterfacePreview(context, attributeSyntax);
        }

        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public override bool IsIndirectAspectsCompliantOnInterface(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            // Determined whether DependencyObject inheritance should be performed
            if (indirectAspectHandler is InheritAspectHandler
                || indirectAttributeSyntax.IsOfType<SkipInheritAttribute>())
            {
                this.skipInheritance = true;
            }

            return !(indirectAspectHandler is MapCommandAspectHandler
                || indirectAspectHandler is DefaultValueAspectHandler
                || indirectAspectHandler is CheckSetterHasChangesAspectHandler
                || indirectAspectHandler is HookPropertySetAspectHandler);
        }

        /// <summary>
        /// Determines whether the aspect is known and could be handled by aspect handler.
        /// </summary>
        /// <param name="context"> </param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        public override bool HandleOnPropertyPreview(AspectDispatcher context, AttributeSyntax attributeSyntax)
        {
            // Reset local fields to avoid side effects with other members
            this.skipInheritance = false;
            return base.HandleOnPropertyPreview(context, attributeSyntax);
        }

        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public override bool IsIndirectAspectsCompliantOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            // Determined whether DependencyObject inheritance should be performed
            if (indirectAspectHandler is InheritAspectHandler
                || indirectAttributeSyntax.IsOfType<SkipInheritAttribute>())
            {
                this.skipInheritance = true;
            }

            return !(indirectAspectHandler is MapCommandAspectHandler 
                || indirectAspectHandler is DefaultValueAspectHandler
                || indirectAspectHandler is CheckSetterHasChangesAspectHandler
                || indirectAspectHandler is HookPropertySetAspectHandler);
        }

        /// <summary>
        /// Handles the on member.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="propertyDeclarationSyntax"> </param>
        /// <param name="handlerStreams"> </param>
        /// <returns>
        /// True if the aspect is handled, false otherwise
        /// </returns>
        public override void HandleOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, PropertyDeclarationSyntax propertyDeclarationSyntax, IDictionary<PropertyDeclarationSyntax, AspectTuple[]> handlerStreams)
        {
            var currentProperty = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.CurrentCodeMemberPropertyDeclaration;
            var currentField = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.CurrentCodeMemberFieldDeclaration;
            var currentType = context.WeavedCodeContext.CurrentTypeDeclaration;

            var dependencyPropertyArguments = new List<CodeExpression>
                    {
                        new CodePrimitiveExpression(currentProperty.Name),
                        new CodeTypeOfExpression(currentProperty.Type),
                        new CodeTypeOfExpression(currentType.Name)
                    };

            // CallBack implementation
            if (attributeSyntax.HasParameter("HasPropertyChangedCallBack")
                && attributeSyntax.GetParameterValue<bool>("HasPropertyChangedCallBack"))
            {
                // hook method declaration
                var hookMethodName = string.Format("On{0}PropertyChangedCallBack", currentProperty.Name);
                var hookParameterTypeText = propertyDeclarationSyntax.Type.ToFullString().Trim();
                currentType.AddHookMethod(hookMethodName,
                                          new Tuple<string, string>(hookParameterTypeText, "oldValue"),
                                          new Tuple<string, string>(hookParameterTypeText, "newValue"));

                // Dependency property call back method
                var callBackMethod = new CodeMemberMethod
                    {
                        Name = string.Format("{0}PropertyChangedCallBack", currentProperty.Name),
                        Attributes = MemberAttributes.Private | MemberAttributes.Static
                    };
                callBackMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(DependencyObject), "d"));
                callBackMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(DependencyPropertyChangedEventArgs), "e"));
                callBackMethod.Statements.Add(
                    new CodeMethodInvokeExpression(
                        new CodeCastExpression(currentType.Name, new CodeVariableReferenceExpression("d")),
                        hookMethodName,
                        new CodeExpression[]
                                {
                                    new CodeCastExpression(currentProperty.Type, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("e"), "OldValue")), 
                                    new CodeCastExpression(currentProperty.Type, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("e"), "NewValue"))
                                }
                    ));
                currentType.Members.Add(callBackMethod);

                // Dependency property metadata parameter
                var metadata = new CodeObjectCreateExpression(
                    typeof(PropertyMetadata), new CodeVariableReferenceExpression(callBackMethod.Name));
                dependencyPropertyArguments.Add(metadata);
            }

            var dependencyProperty = new CodeMemberField("readonly " + typeof(DependencyProperty).FullName, currentProperty.Name + "Property")
                {
                    Attributes = MemberAttributes.Public | MemberAttributes.Static,
                    InitExpression = new CodeMethodInvokeExpression(
                        new CodeTypeReferenceExpression(typeof(DependencyProperty)),
                        "Register", dependencyPropertyArguments.ToArray())
                };

            context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.UpdateMemberField(dependencyProperty);
            currentType.Members.Remove(currentField);
            currentType.Members.Add(dependencyProperty);

            // Getter management
            if (currentProperty.HasGet)
            {
                currentProperty.GetStatements.Clear();
                currentProperty.GetStatements.Add(
                    new CodeMethodReturnStatement(
                        new CodeCastExpression(
                            currentProperty.Type,
                            new CodeMethodInvokeExpression(
                                new CodeThisReferenceExpression(),
                                "GetValue",
                                new CodeFieldReferenceExpression(
                                    null,
                                    currentProperty.Name + "Property")))));
            }

            // Setter management
            if (currentProperty.HasSet
                && context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterCodeCoreStream != null
                && context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterAssignStatment != null)
            {
                var setterStream = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterCodeCoreStream;
                var setValueExpression = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterAssignStatment;

                if (setterStream.Contains(setValueExpression))
                {
                    var index = setterStream.IndexOf(setValueExpression);
                    setterStream[index] =
                        new CodeExpressionStatement(
                            new CodeMethodInvokeExpression(
                                new CodeThisReferenceExpression(),
                                "SetValue",
                                new CodeFieldReferenceExpression(
                                    null,
                                    currentProperty.Name + "Property"),
                                    new CodePropertySetValueReferenceExpression()));

                    // Context synchronization
                    context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.UpdateSetterAssignExpression(setterStream[index]);
                }
            }

            // DependencyObject Inheritance
            if (!currentType.IsInheriting<DependencyObject>() 
                && context.ParsedCodeContext.CurrentInterfaceSyntax.AttributeLists
                    .SelectMany(p=>p.Attributes)
                    .All(p=>!p.IsOfType<SkipInheritAttribute>()
                        && !p.IsOfType<InheritAttribute>()))
            {
                if (currentType.BaseTypes.Count > 0)
                {
                    currentType.BaseTypes.Insert(0, new CodeTypeReference(typeof(DependencyObject)));
                }
                else
                {
                    currentType.BaseTypes.Add(typeof(DependencyObject));
                }
            }
        }
    }
}